#ifndef __tenacitas_log_core__internal_logger__h__
#define __tenacitas_log_core__internal_logger__h__



/** @file */


/** @todo */

// ==> C++ Headers
#include <string>
#include <cstdint>
#include <utility>
#include <memory>
#include <thread>
#include <functional>
#include <map>
#include <mutex>

// ==> 3rds Headers

// ==> Our Headers
#include <tenacitas.return_code.core/rc.h>

#include "../level.h"
#include "../thread_id.h"

// ==> Namespaces
using namespace std;

// ==> Macro Constants

// ==> Macro Commands


/** ==> namespace of the organization */
namespace tenacitas {

    /** ==> namespace of the project */
    namespace log {

        namespace core {

            // ==> Typedefs 

            // ==> Pre-Declarations

            // ==> Attributes 

            // ==> Functions

            // ==> Classes
            template <typename supplier>
            class internal_logger {

            public:

                // ==> internal_logger - Public Internal Types

                /** */
                typedef typename supplier::media media;

                /** */
                enum constants : constant_id {could_not_create_new_line=100,
                        could_not_write_data=101,
                        could_not_create_media=102};


                // ==> internal_logger - Public Constructors
                
                /** Destructor */
                ~internal_logger( );                

                /** */
                internal_logger(const level & p_level,
                                const thread_id & p_thread_id);

                /** */
                internal_logger(internal_logger && p_implemention) noexcept;

                /** */
                internal_logger(const internal_logger & ) = delete;

                // ==> internal_logger - Public Accessors

                /** Informs if a message log can be logged

                    @param  p_level is the level one wants to check if a log
                    will be logged

                    @attention <b>tenacitas::log::level::error and
                    tenacitas::log::level::fatal will allways be logged, no
                    matter the current log level. So, it is not necessary to
                    check for the log level if one wants to use @p log_error
                    or @p log_fatal</b> 
                */
                bool can_log(const level & p_level) const noexcept;

                /** */
                void set_level(const level & p_level) noexcept;

                /** */
                const level & get_level() const noexcept;

                /** */
                thread_id get_thread_id() const noexcept;

                /** Sets this internal_loggers as unused */
                void set_unused() noexcept;

                /** Checks if this internal_logger is unused */
                bool is_unused() const noexcept;                    

                /** */
                static void set_level_all(const level & p_level) noexcept;

                /** */
                static const level & get_level_all() noexcept;


                // ==> internal_logger - Public Processors

                /** */
                rc<> new_line(const level & p_level,
                              const char * p_file,
                              int64_t p_line) noexcept;

                // ==> internal_logger - Public Operators

                /** */
                template <typename t_data>
                internal_logger & operator << (const t_data & p_data);

                /** */
                template <typename t_data>
                internal_logger & operator << (t_data && p_data);

                /** */
                internal_logger & operator=(internal_logger &&) noexcept;

                /** */
                bool operator==(const internal_logger &) const noexcept;

                /** */
                bool operator==(internal_logger &&) const noexcept;

                // ==> internal_logger - Public  Attributes

                /** */
                static const class_id id = 14327581585920ULL;


            private:
                // ==> internal_logger - Private Attributes

                /** Log level of this @p logger object */
                level m_level;

                /** Thread id of the thread for whom log messages are being
                    created */ 
                thread_id m_thread_id;

                /** Log level of the current log line */
                level m_line_level;

                /** Class that actually logs the message */
                media m_media;

                /** Indicates that this internal_logger is no longer being
                    used */
                bool m_unused;
                    
                /** Log level commom to all the @p logger objects */
                static level m_level_all;

            };
        }
    }
}
// =============================================================================
// I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::log::core;

// -----------------------------------------------------------------------------
template <typename supplier>
const class_id
internal_logger<supplier>::
id;


// -----------------------------------------------------------------------------
template <typename supplier>
level
internal_logger<supplier>::
m_level_all(level::info);

// -----------------------------------------------------------------------------
template <typename supplier>
inline internal_logger<supplier>::
internal_logger(const level & p_level,
                const thread_id & p_thread_id) 
    : m_level(p_level),
      m_thread_id(p_thread_id),
      m_line_level(p_level),
      m_media(p_thread_id),
      m_unused(false){}

// -----------------------------------------------------------------------------
template <typename supplier>
inline internal_logger<supplier>::
internal_logger(internal_logger && p_implemention) noexcept
    : m_level(p_implemention.m_level),
      m_thread_id(p_implemention.m_thread_id),
      m_media(std::move(p_implemention.m_media)),
      m_unused(false){}

// -----------------------------------------------------------------------------
template <typename supplier>inline internal_logger<supplier>::
~internal_logger() {}

// -----------------------------------------------------------------------------
template <typename supplier>
inline bool
internal_logger<supplier>::
can_log(const level & p_level) const noexcept {
    if ((p_level == level::error) || (p_level == level::fatal)) {
        return true;
    }
    return (p_level >= m_level);
}


// -----------------------------------------------------------------------------
template <typename supplier>
inline void
internal_logger<supplier>::
set_level(const level & p_level) noexcept {
    m_line_level = m_level = p_level;
}

// -----------------------------------------------------------------------------
template <typename supplier>
inline const level &
internal_logger<supplier>::
get_level() const noexcept {
    return m_level;
}

// -----------------------------------------------------------------------------
template <typename supplier>
thread_id
internal_logger<supplier>::
get_thread_id() const noexcept {return m_thread_id;}

// -----------------------------------------------------------------------------
template <typename supplier>
inline void
internal_logger<supplier>::
set_level_all(const level & p_level) noexcept {
    m_level_all = p_level;
}

// -----------------------------------------------------------------------------
template <typename supplier>
inline const level &
internal_logger<supplier>::
get_level_all() noexcept {
    return m_level_all;
}

// -----------------------------------------------------------------------------
template <typename supplier>
inline void
internal_logger<supplier>::
set_unused() noexcept {m_unused = true;}

// -----------------------------------------------------------------------------
template <typename supplier>
inline bool
internal_logger<supplier>::
is_unused() const noexcept {return m_unused;}

// -----------------------------------------------------------------------------
template <typename supplier>
rc<>
internal_logger<supplier>::
new_line(const level & p_level,
         const char * p_file,
         int64_t p_line) noexcept {

    m_line_level = p_level;

    rc<> _rc;
    
    try {
        uint64_t _timestamp =
            std::move(std::chrono::nanoseconds(std::chrono::
                                               high_resolution_clock::
                                               now().time_since_epoch()).count());
        
        if (can_log(m_line_level)) {
            _rc = std::move(m_media.new_line(p_level,
                                             _timestamp, p_file, p_line));
        }
    }
    catch (std::exception & _ex) {
        _rc = rc<>(id, constants::could_not_create_new_line,
                   __FILE__, __LINE__, _ex.what());
    }
    return _rc;
}

// -----------------------------------------------------------------------------
template <typename supplier>
template <typename t_data>
internal_logger<supplier> &
internal_logger<supplier>::
operator << (const t_data & p_data) {

    if (can_log(m_line_level)) {
        rc <> _rc(m_media.write(p_data));
        if (!_rc) {
            throw _rc;
        }        
    }
    return *this;
}

// -----------------------------------------------------------------------------
template <typename supplier>
template <typename t_data>
internal_logger<supplier> &
internal_logger<supplier>::
operator << (t_data && p_data) {

    if (can_log(m_line_level)) {
        rc<> _rc(m_media.write(p_data));
        if (!_rc) {
            throw _rc;
        }
    }
    return *this;
}


// -----------------------------------------------------------------------------
template <typename supplier>
internal_logger<supplier> &
internal_logger<supplier>::
operator=(internal_logger && p_current) noexcept {
    if (this != &p_current) {
        m_level = std::move(p_current.m_level);
        m_thread_id = std::move(p_current.m_thread_id);
        m_media = std::move(p_current.m_media);
        m_unused = std::move(p_current.m_unused);
    }
    return *this;
}

// -----------------------------------------------------------------------------
template <typename supplier>
bool
internal_logger<supplier>::
operator==(const internal_logger & p_current) const noexcept {
    if (this != & p_current) {
        return (this->m_thread_id == p_current.m_thread_id);
    }
    return true;
}

                

// -----------------------------------------------------------------------------
template <typename supplier>
bool
internal_logger<supplier>::
operator==(internal_logger && p_current) const noexcept {
    if (this != & p_current) {
        return (this->m_thread_id == p_current.m_thread_id);
    }
    return true;
}



#endif
